#include "render_dx9.h"
#include "core.h"
#include "log.h"
#include "ivbuffer_dx9.h"
#include <d3dx9.h>

render_dx9::render_dx9()
{
	m_bpp = 32;
	m_d3d = 0;
	m_d3ddevice = 0;
}

render_dx9::~render_dx9()
{
	free();
}

void render_dx9::init(xml_node& _node)
{
	m_width = _node.attr_int("width",core::get_inst().get_window()->width());
	m_height = _node.attr_int("height",core::get_inst().get_window()->height());
	m_windowed = !(_node.attr_bool("fullscreen",false));

	m_d3d = Direct3DCreate9(D3D_SDK_VERSION);

	if(m_d3d==NULL)
		LOG_FATAL("Unable create Direct3D context.");

	HWND hwnd = core::get_inst().get_window()->hwnd();

	memset(&m_d3dpp,0,sizeof(D3DPRESENT_PARAMETERS)); 
	m_d3dpp.BackBufferCount = 1;
	m_d3dpp.BackBufferWidth = m_width;
	m_d3dpp.BackBufferHeight = m_height;
	m_d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; 
	m_d3dpp.Windowed = m_windowed; 
	m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 
	m_d3dpp.hDeviceWindow = hwnd;
	m_d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8;
	m_d3dpp.EnableAutoDepthStencil = true;
	m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	m_d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;

	if(!m_windowed)
		m_d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;

	if(FAILED(m_d3d->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hwnd,D3DCREATE_HARDWARE_VERTEXPROCESSING,&m_d3dpp,&m_d3ddevice)))
	{ 
		LOG_ERROR("Unable to create Direct3D device with hardware vertex processing! Switching to software vertex processing...");

		if(FAILED(m_d3d->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hwnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&m_d3dpp,&m_d3ddevice)))
		{ 
			LOG_FATAL("Unable to create Direct3D device!");
		}
	} 

	matrix4 ident;
	ident.identity();

	D3DCHECK(m_d3ddevice->SetTransform(D3DTS_PROJECTION,(D3DMATRIX*)&ident)); 
	D3DCHECK(m_d3ddevice->SetTransform(D3DTS_VIEW,(D3DMATRIX*)&ident)); 
	D3DCHECK(m_d3ddevice->SetTransform(D3DTS_WORLD,(D3DMATRIX*)&ident)); 

	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_LIGHTING,FALSE));

	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_ZWRITEENABLE,TRUE));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA)); 
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_ALPHAFUNC,D3DCMP_GREATEREQUAL));
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_ALPHAREF,(DWORD)128));

	D3DCHECK(m_d3ddevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_NONE));
	D3DCHECK(m_d3ddevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_NONE));

	bool shader_enable = _node.attr_bool("shaders",false);

	if(shader_enable)
	{
		//#TODO
	}
}

void render_dx9::reset()
{
	D3DCHECK(m_d3ddevice->Reset(&m_d3dpp));
}

void render_dx9::free()
{
	if(m_d3ddevice)
	{
		m_d3ddevice->Release();
		m_d3ddevice = 0;
	}
	if(m_d3d)
	{
		m_d3d->Release();
		m_d3d = 0;
	}
}

device_state render_dx9::get_state()
{
	switch(m_d3ddevice->TestCooperativeLevel())
	{
	case D3D_OK:				return device_ok;
	case D3DERR_DEVICELOST:		return device_lost;
	default:					return device_error;
	}
}

void render_dx9::begin()
{
	D3DCHECK(m_d3ddevice->BeginScene());
}

void render_dx9::end()
{
	D3DCHECK(m_d3ddevice->EndScene());
	D3DCHECK(m_d3ddevice->Present(NULL, NULL, NULL, NULL));
}

void render_dx9::clear(u32 _type, color _color, float _z, u32 _stencil)
{
	D3DCHECK(m_d3ddevice->Clear(0,0,_type,_color.argb(),_z,_stencil));
}

vbuffer* render_dx9::create_vbuffer(buffer_usage _usage,u32 _vformat,u32 _blocksize,u32 _lenth)
{
	return new vbuffer_dx9(m_d3ddevice,_lenth,_usage,_vformat,_blocksize);
}

ibuffer* render_dx9::create_ibuffer(buffer_usage _usage,u32 _size)
{
	return new ibuffer_dx9(m_d3ddevice,_size,_usage);
}

void render_dx9::set_vbuffer(vbuffer* _vbuffer)
{
	vbuffer_dx9* vb = (vbuffer_dx9*)_vbuffer;
	D3DCHECK(m_d3ddevice->SetFVF(vb->m_format));
	D3DCHECK(m_d3ddevice->SetStreamSource(0,vb->m_d3dvbuffer,0,vb->m_block_size));
}

void render_dx9::set_ibuffer(ibuffer* _ibuffer)
{
	if(_ibuffer)
	{
		ibuffer_dx9* ib = (ibuffer_dx9*)_ibuffer;
		D3DCHECK(m_d3ddevice->SetIndices(ib->m_d3dibuffer));
	}
	else
	{
		D3DCHECK(m_d3ddevice->SetIndices(0));
	}
}

void render_dx9::draw_vbuffer(primitiv_type _prtype,u32 _start_vrt,u32 _pr_count)
{
	D3DCHECK(m_d3ddevice->DrawPrimitive((D3DPRIMITIVETYPE)_prtype,_start_vrt,_pr_count));
}

void render_dx9::draw_ibuffer(primitiv_type _prtype,u32 _num_vtx,u32 _start_idx,u32 _pr_count)
{
	D3DCHECK(m_d3ddevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)_prtype,0,0,_num_vtx,_start_idx,_pr_count));
}

texture* render_dx9::create_texture(u32 _w, u32 _h,tex_format _fmt)
{
	texture_dx9* tex = new texture_dx9();

	m_d3ddevice->CreateTexture(_w,_h,1,0,D3DFMT_DXT1,D3DPOOL_MANAGED,&(tex->m_texture),0);

	tex->m_width = _w;
	tex->m_height = _h;

	return (texture*)tex;
}

void render_dx9::set_texture(u32 _stage,texture* _texture)
{
	if(!_texture)
		D3DCHECK(m_d3ddevice->SetTexture(_stage,0))
	else
	{
		texture_dx9* t = (texture_dx9*)_texture;
		D3DCHECK(m_d3ddevice->SetTexture(_stage,t->m_texture));
	}
}

effect* render_dx9::create_effect(file* _file,const char* _vfunc,const char* _pfunc)
{
	if(!_file)
		LOG_ERROR("Unable to create effect, wrong file.");
	char* buffer = new char[_file->size()+1];
	_file->read(buffer,_file->size());
	buffer[_file->size()]='\0';
	effect* e = new effect(buffer,_vfunc,_pfunc);
	delete buffer;
	return e;
}

effect* render_dx9::create_effect(const char* _fname,const char* _vfunc,const char* _pfunc)
{
	file f;
	if(f.open(_fname)==-1)
		LOG_ERROR("Unable to create effect, wrong file.");
	char* buffer = new char[f.size()+1];
	f.read(buffer,f.size());
	buffer[f.size()]='\0';
	effect* e = new effect(buffer,_vfunc,_pfunc);
	delete buffer;
	f.close();
	return e;
}

void render_dx9::set_effect(effect* _effect)
{
	_effect->bind();
}

void render_dx9::enable_light(bool _enabled)
{
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_LIGHTING,_enabled?TRUE:FALSE));
}

void render_dx9::set_light(u32 _stage,light _light)
{
	if(_light.type!=light_none)
	{
		D3DLIGHT9 l;
		ZeroMemory(&l,sizeof(D3DLIGHT9));
		l.Type       = (D3DLIGHTTYPE)_light.type;
		l.Diffuse.a    = _light.diffuse.a;
		l.Diffuse.r    = _light.diffuse.r;
		l.Diffuse.g    = _light.diffuse.g;
		l.Diffuse.b    = _light.diffuse.b;
		l.Ambient.a    = _light.ambient.a;
		l.Ambient.r    = _light.ambient.r;
		l.Ambient.g    = _light.ambient.g;
		l.Ambient.b    = _light.ambient.b;
		l.Direction.x  = _light.direction.x;
		l.Direction.y  = _light.direction.y;
		l.Direction.z  = _light.direction.z;

		D3DCHECK(m_d3ddevice->SetLight(_stage,&l));
		D3DCHECK(m_d3ddevice->LightEnable(_stage,TRUE));
	}
	else
		D3DCHECK(m_d3ddevice->LightEnable(_stage,FALSE));
}

void render_dx9::set_material(material _material)
{
	D3DMATERIAL9 m;
	ZeroMemory(&m,sizeof(D3DMATERIAL9));
	m.Ambient.a = _material.ambient.a;
	m.Ambient.r = _material.ambient.r;
	m.Ambient.g = _material.ambient.g;
	m.Ambient.b = _material.ambient.b;
	m.Diffuse.a = _material.diffuse.a;
	m.Diffuse.r = _material.diffuse.r;
	m.Diffuse.g = _material.diffuse.g;
	m.Diffuse.b = _material.diffuse.b;
	m.Specular.a = _material.specular.a;
	m.Specular.r = _material.specular.r;
	m.Specular.g = _material.specular.g;
	m.Specular.b = _material.specular.b;
	m.Emissive.a = _material.emission.a;
	m.Emissive.r = _material.emission.r;
	m.Emissive.g = _material.emission.g;
	m.Emissive.b = _material.emission.b;
	m.Power = _material.shininess;
	D3DCHECK(m_d3ddevice->SetMaterial(&m));
}

void render_dx9::set_ambient(color _color)
{
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_AMBIENT,_color.argb()));
}

void render_dx9::set_cullmode(cull_mode _mode)
{
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_CULLMODE,_mode));
}

void render_dx9::set_fillmode(fill_mode _mode)
{
	D3DCHECK(m_d3ddevice->SetRenderState(D3DRS_FILLMODE,_mode));
}

void render_dx9::set_projection(matrix4 _matrix)
{
	D3DCHECK(m_d3ddevice->SetTransform(D3DTS_PROJECTION,(D3DMATRIX*)&_matrix)); 
}

void render_dx9::set_view(matrix4 _matrix)
{
	D3DCHECK(m_d3ddevice->SetTransform(D3DTS_VIEW,(D3DMATRIX*)&_matrix)); 
}

void render_dx9::set_world(matrix4 _matrix)
{
	D3DCHECK(m_d3ddevice->SetTransform(D3DTS_WORLD,(D3DMATRIX*)&_matrix)); 
}

void render_dx9::set_viewport(u32 _x,u32 _y,u32 _width,u32 _height)
{
	D3DVIEWPORT9 viewport;
	viewport.X = _x;
	viewport.Y = _y;
	viewport.Width = _width;
	viewport.Height = _height;
	viewport.MinZ = 0.0f;
	viewport.MaxZ = 1.0f;
	D3DCHECK(m_d3ddevice->SetViewport(&viewport));
}

IDirect3DDevice9* render_dx9::get_device()
{
	return m_d3ddevice;
}
